<template>
    <MyArticle title="Java学习笔记之六：泛型程序设计" date="2024年03月10日">
        <div class="_article-content">
            <p>泛型程序设计（generic programming）意味着编写的代码可以对多种不同类型的对象重用。类型参数会让你的程序更易读，也更安全。</p>
            <p>泛型类是有一个或多个类型变量的类：</p>
            <pre><code>public class Pair&lt;T&gt;{
    private T first;
}
public class Pair&lt;T, U&gt;{}</code></pre>
            <p>类型变量在整个类定义中用于指定方法的返回类型以及字段和局部变量的类型。类型变量使用大写字母，E表示集合的元素类型，K和V分别表示键和值的类型，T、S、U表示任意类型。</p>
            <p>泛型方法：</p>
            <pre><code>public class ArrayAlg{
    public static &lt;T&gt; T getMiddle(T[] a){
        return a[a.length / 2];
    }
}</code></pre>
            <p>调用：</p>
            <pre><code>String middle = ArrayAlg.&lt;String&gt;getMiddle("John", "lucy", "public");
// 简化
String middle = ArrayAlg.getMiddle("John", "lucy", "public");</code></pre>
            <p>类型变量放在修饰符的后面，并在返回类型的前面。可以在普通类中定义泛型方法，也可以在泛型类中定义。</p>
            <p>限定类型变量：&lt;T extends BoundingType&gt;，T和限定类型可以是类，也可以是接口。</p>
            <pre><code>public static &lt;T extends Comparable&gt; T min(T[] a) { ... }</code></pre>
            <p>一个类型变量或通配符可以有多个限定：</p>
            <pre><code>T extends Comparable & Serializable</code></pre>
            <p>可以根据需要拥有多个接口超类型，但最多有一个限定可以是类。如果有一个类作为限定，它必须是限定列表中的第一个限定。</p>
            <pre><code>public static &lt;T extends Comparable&gt; Pair&lt;T&gt; minmax(T[] arr){
    if (arr == null || arr.length == 0) {
        return null;
    }
    T min = arr[0];
    T max = arr[0];
    for (int i = 1; i < arr.length; i++) {
        if (min.compareTo(arr[i]) > 0) {
            min = arr[i];
        }
        if (max.compareTo(arr[i]) < 0) {
            max = arr[i];
        }
    }
    return new Pair&lt;T&gt;(min, max);
}</code></pre>
            <p>虚拟机没有泛型类型对象，所有对象都属于普通类。无论何时定义一个泛型类型，都会自动提供一个相应的原始类型，这个原始类型的名字就是去掉类型参数后的泛型类型名，类型变量会被擦除，并替换为其限定类型。对于无限定的变量则替换为Object。</p>
            <pre><code>public class Pair{
    private Object first;
    private Object second;
    public Pair(Object first, Object second){
        this.first = first;
        this.second = second;
    }
}</code></pre>
            <p>原始类型用第一个限定来替换类型变量，或者，如果没有给定限定，就替换为Object。</p>
            <p>注解@SuppressWarnings("unchecked")会关闭对方法中所有代码的检查。</p>
            <h3 class="title">限制与局限性</h3>
            <p>不能用基本类型实例化类型参数；如果试图查询一个对象是否属于某个泛型类型，会得到一个编译器错误或者得到一个警告。</p>
            <pre><code>if (a instanceof Pair&lt;String&gt;) // ERROR
if (a instanceof Pair&lt;T&gt;) // ERROR</code></pre>
            <p>getClass方法总是返回原始类型：</p>
            <pre><code>Pair&lt;String&gt; stringPair = ...
Pair&lt;Employee&gt; employeePair = ...
if (stringPair.getClass() == employeePair.getClass()) { // ALWAYS TRUE
    System.out.println("Same!");
}</code></pre>
            <p>不能实例化参数化类型的数组：</p>
            <pre><code>var table = new Pair&lt;String&gt;[10]; // ERROR</code></pre>
            <p>如果需要收集参数化类型对象，可以使用ArrayList：</p>
            <pre><code>ArrayList&lt;Pair&lt;String&gt;&gt;</code></pre>
            <p>不能在类似new T(...)的表达式中使用类型变量：</p>
            <pre><code>public Pair() {
    first = new T(); // ERROR
    second = new T(); // ERROR
}</code></pre>
            <p>Java8之后，最好的解决办法是让调用者提供一个构造器表达式：</p>
            <pre><code>Pair&lt;String&gt; p = Pair.makePair(String::new);
public static &lt;T&gt; Pair&lt;T&gt; makePair(Supplier&lt;T&gt; constr) {
    return new Pair&lt;T&gt;(constr.get(), constr.get());
}</code></pre>
            <p>或者通过反射：</p>
            <pre><code>public static &lt;T&gt; Pair&lt;T&gt; makePair(Class&lt;T&gt; cl) {
    try {
        Constructor&lt;T&gt; c = cl.getConstructor();
        return new Pair&lt;T&gt;(c.newInstance(), c.newInstance());
    } catch (Exception e) {
        throw new RuntimeException(e);
    }
}
Pair&lt;String&gt; p = Pair.makePair(String.class);</code></pre>
            <p>Class类本身是泛型的，String.class是Class&lt;String&gt;的一个实例。</p>
            <p>不能构造泛型数组：</p>
            <pre><code>public static &lt;T extends Comparable&gt; T[] minmax(T...a){
    T[] mm = new T[2]; // ERROR
}</code></pre>
            <p>泛型类不能在静态字段或方法中引用类型变量：</p>
            <pre><code>public class Singleton&lt;T&gt;{
    private static T instance; // ERROR
    public static T getInstance(){ // ERROR
        if (instance == null) {
            instance = new T();
        }
        return instance;
    }
}</code></pre>
            <p>不能抛出或捕获泛型类的实例，甚至泛型类扩展Throwable都是不合法的：</p>
            <pre><code>public class Problem&lt;T&gt; extends Exception{ // ERROR
}</code></pre>
            <p>catch子句中不能使用类型变量，以下方法将不能编译：</p>
            <pre><code>public static &lt;T entends Throwable&gt; void doWork(class&lt;T&gt; t){
    try{
        ...
    }catch(T e){ // ERROR
        ...
    }
}</code></pre>
            <p>在异常规范中使用类型变量是允许的，以下方法是合法的：</p>
            <pre><code>public static &lt;T extends Throwable&gt; void doWork(class&lt;T&gt; t) throws T{
    try{
        ...
    } catch(Throwable realCause){
    }
}</code></pre>
            <p>倘若两个接口类型是同一接口的不同参数化, 一个类或类型变量就不能同时作为这两个接口类型的子类：</p>
            <pre><code>class Employee implements Comparable&lt;Employee&gt;{}
class Manager extends Employee implaments Comparable&lt;Manager&gt;{} // ERROR</code></pre>
            <p>Manager不能同时实现Comparable&lt;Employee&gt;和Comparable&lt;Manager&gt;。</p>
            <p>一般来讲，无论S与T有什么关系，Pair&lt;S&gt;与Pair&lt;T&gt;都没有任何关系。</p>
            <p>通配符类型 Pair&lt;? extends Employee&gt;表示Pair类型，它的类型参数是Employee的子类，如Pair&lt;Manager&gt;，但不能是Pair&lt;String&gt;。</p>
            <p>通配符的超类型限定：</p>
            <pre><code>? super Manager // 限制为Manager的所有超类型</code></pre>
            <p>直观地讲，带有超类型限定的通配符允许你写入一个泛型对象，而带有子类型限定的通配符允许你读取一个泛型对象。</p>
        </div>
    </MyArticle>
</template>
